/*

Miranda IM: the free IM client for Microsoft* Windows*

Copyright 2000-2007 Miranda ICQ/IM project, 
all portions of this codebase are copyrighted to the people 
listed in contributors.txt.

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
aLONG with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

*/

/**
 * Local Includes:
 **/
#include "commonheaders.h"
#include "ctrl.h"
#include "m_flags.h"

/**
 * Miranda Includes:
 **/

/****************************************************************************
 *	common methods
 ****************************************************************************/

/**
 * name:	ComboBox_Destroy
 * desc:	destroys info structure associated with an edit control
 * param:	hDlg	- owning propertysheetpage dialog
 *			idComboBox	- control identifier for the combobox
 *
 * return	nothing
 **/
VOID ComboBox_Destroy(HWND hDlg, INT idComboBox)
{
	HWND hComboBox = GetDlgItem(hDlg, idComboBox);
	LPCBCTRL pcbCtrl;
	// deleite info structure of combobox
	if(PtrIsValid(pcbCtrl = (LPCBCTRL)GetUserData(hComboBox))) {
		if(!(pcbCtrl->wFlags & CBF_INITIALIZED)) {
			LPIDSTRLIST pItem = (LPIDSTRLIST)ComboBox_GetItemData(hComboBox, 0);
			if(pItem) {
				FREE(pItem->szText);
				free(pItem);
			}
		}
		free(pcbCtrl);	
	}
}

/**
 * name:	ComboBox_AddItem
 * desc:	Adds a string and associated item data to a combobox.
 *			Item data must be valid!
 * param:	hComboBox	- handle to the combobox's window
 *			pszText		- the text to add to the combobox
 *			lParam		- item data to accociate with the new item
 *
 * return:	zero-based index to new item or CB_ERR on failure
 **/
INT ComboBox_AddItem(HWND hComboBox, LPCTSTR pszText, LPARAM lParam)
{
	if(PtrIsValid(lParam)) {
		INT added = ComboBox_AddString(hComboBox, pszText);
		if(SUCCEEDED(added)) {
			if(SUCCEEDED(ComboBox_SetItemData(hComboBox, added, lParam))) 
				return added;
			ComboBox_DeleteString(hComboBox, added);
		}
	}
	return CB_ERR;
}

/**
 * name:	ComboBox_InsertItem
 * desc:	Adds a string and associated item data to a combobox.
 *			Item data must be valid!
 * param:	hComboBox	- handle to the combobox's window
 *			pszText		- the text to add to the combobox
 *			lParam		- item data to accociate with the new item
 *
 * return:	zero-based index to new item or CB_ERR on failure
 **/
INT ComboBox_InsertItem(HWND hComboBox, INT nIndex, LPCTSTR pszText, LPARAM lParam)
{
	if(PtrIsValid(lParam)) {
		INT added = ComboBox_InsertString(hComboBox, nIndex, pszText);
		if(SUCCEEDED(added)) {
			if(SUCCEEDED(ComboBox_SetItemData(hComboBox, added, lParam))) 
				return added;
			ComboBox_DeleteString(hComboBox, added);
		}
	}
	return CB_ERR;
}

/**
 * name:	ComboBox_RemoveItem
 * desc:	removes item at nIndex from combobox's listview and returns its item data
 * param:	hComboBox	- handle to the combobox's window
 *			nIndex		- index of item to remove
 *
 * return:	item data of removed item or NULL on failure
 **/
LPARAM ComboBox_RemoveItem(HWND hComboBox, INT nIndex)
{
	LPARAM lParam = NULL;
	if(nIndex >= 0) {
		if(!PtrIsValid(lParam = ComboBox_GetItemData(hComboBox, nIndex)))
			lParam = NULL;
		if(FAILED(ComboBox_DeleteString(hComboBox, nIndex)))
			lParam = NULL;
	}
	return lParam;
}

/**
 * name:	ComboBox_SelChanged
 * desc:	handle selchange notification of static comboboxes like country lists
 * param:	hDlg	  - window handle of propertysheetpage
 *			hComboBox - handle of combobox
 *
 * return:	nothing
 **/
VOID ComboBox_SelChanged(HWND hDlg, HWND hComboBox)
{
	LPCBCTRL pcbCtrl = (LPCBCTRL)GetUserData(hComboBox);
	INT nCurSel = ComboBox_GetCurSel(hComboBox);

	if(PtrIsValid(pcbCtrl) && !(pcbCtrl->wFlags & CTRLF_CHANGED) &&	nCurSel >= 0 && pcbCtrl->nTopItem != nCurSel) {
		pcbCtrl->wFlags |= CTRLF_CHANGED;
		SendMessage(GetParent(hDlg), PSM_CHANGED, 0, 0);
	}
}

/**
 * name:	ComboBox_CountrySelChanged
 * desc:	handle selchange notification of static comboboxes like country lists
 * param:	hDlg	  - window handle of propertysheetpage
 *			hComboBox - handle of combobox
 *
 * return:	nothing
 **/
VOID ComboBox_CountrySelChanged(HWND hDlg, HWND hComboBox, HWND hIconCtrl)
{
	INT nCurSel = ComboBox_GetCurSel(hComboBox);

	if(nCurSel >= 0) {
		if(hIconCtrl && ServiceExists(MS_FLAGS_LOADFLAGICON)) {
			IDSTRLIST *pCountry = (IDSTRLIST *)ComboBox_GetItemData(hComboBox, nCurSel);
			if(pCountry) {
				HICON hIco = (HICON)CallService(MS_FLAGS_LOADFLAGICON, pCountry->nID, 0);

				if(hIco) {
					SendMessage(hIconCtrl, STM_SETIMAGE, IMAGE_ICON, (LPARAM)hIco);
				}
			}
		}

		if(!PspIsLocked(hDlg)) {
			LPCBCTRL pcbCtrl = (LPCBCTRL)GetUserData(hComboBox);

			if(PtrIsValid(pcbCtrl) && !(pcbCtrl->wFlags & CTRLF_CHANGED) && pcbCtrl->nTopItem != nCurSel) {
				pcbCtrl->wFlags |= CTRLF_CHANGED;
				SendMessage(GetParent(hDlg), PSM_CHANGED, 0, 0);
			}
		}
	}
}

/**
 * name:	ComboBox_GetItem
 * desc:	read item text and data and return both
 * param:	hComboBox	- handle to combobox
 *			nIndex		- index to item whose data to return
 *			szText		- address of string holding the text
 *			ccText		- max number of chars szText can hold without reallocating
 *			lParam		- pointer retrieving address of data structure
 **/
LPVOID ComboBox_GetItem(HWND hComboBox, INT nIndex, LPTSTR *szText, LPINT ccText, LPARAM *lParam)
{
	// retrieve item data
	if(lParam) {
		*lParam = (LPARAM)ComboBox_GetItemData(hComboBox, nIndex);
	}

	// retrieve item text
	if(szText && ccText) {
		INT ccItem = ComboBox_GetLBTextLen(hComboBox, nIndex) + 6;	// space for text + " SMS" eventually
		
		if(ccItem < *ccText || PtrIsValid(*szText = (LPTSTR)realloc(*szText, (ccItem + 1) * sizeof(TCHAR)))) {
			*ccText = max(ccItem, *ccText);
			if(FAILED(ComboBox_GetLBText(hComboBox, nIndex, *szText)))
				return NULL;
		}
	}
	return (szText ? (LPVOID)*szText : (LPVOID)*lParam);
}

/****************************************************************************
 *	static combobox methods
 ****************************************************************************/

/**
 * name:	ComboBox_AddItems
 * desc:	adds a IdStrList to the combobox. Only way to interact with this combobox is by selecting an item.
 *			Deleting or anything else is not allowed. Be careful to call ComboBox_RemoveItem
 *			or ComboBox_DeleteString but NEVER ComboBox_DeleteItem to delete an item added with this function!!
 * param:	hComboBox		- handle to the combobox's window
 *			pList			- list to add
 *			numItems		- number of items in idList
 *			nUnspecifiedID	- id an item must have to be the "unspecified item"
 *			nSelectedID		- if pszSelected is NULL, this is the id of the item to select
 *			pszSelected		- text of the item to mark as selected
 *
 * return:	true or false
 **/
BOOLEAN ComboBox_AddItems(
			HWND hComboBox,
			LPIDSTRLIST pList,
			UINT numItems,
			INT nUnspecifiedID,
			INT nSelectedID,
			LPCTSTR pszSelected)
{
	INT	add;
	LPIDSTRLIST pUnspec = NULL;
	BOOLEAN bItemIsSelected = FALSE;

	while(PtrIsValid(pList) && numItems-- > 0) {
		// remember item with id indicating "unspecified item", to add at the end
		if(!pUnspec && (nUnspecifiedID == pList->nID)) {
			pUnspec = pList;
		}
		else {
			if(FAILED(add = ComboBox_AddItem(hComboBox, pList->ptszTranslated, (LPARAM)pList))) return FALSE;
			if((!pszSelected && pList->nID == nSelectedID) || (pszSelected && (!_tcscmp(pszSelected, pList->szText) || (pList->ptszTranslated != pList->szText && !_tcscmp(pszSelected, pList->ptszTranslated))))) {
				ComboBox_SetCurSel(hComboBox, add);
				bItemIsSelected = TRUE;
			}
		}
		pList++;
	}
	// add unspecified item
	if(pUnspec) {
		if(FAILED(add = ComboBox_InsertItem(hComboBox, 0, pUnspec->ptszTranslated, (LPARAM)pUnspec))) return FALSE;
		if(!bItemIsSelected) ComboBox_SetCurSel(hComboBox, add);
	}
	return TRUE;
}

/**
 * name:	ComboBox_DropDown
 * desc:	adds the combobox items if not jet done on dropdown message
 * param:	hComboBox - handle to the dropdown control
 *
 * return:	nothing
 **/
VOID ComboBox_DropDown(HWND hComboBox)
{
	LPCBCTRL pcbCtrl;

	if(PtrIsValid(pcbCtrl = (LPCBCTRL)GetUserData(hComboBox)) && !(pcbCtrl->wFlags & CBF_INITIALIZED)) {
		if(ComboBox_GetCount(hComboBox) > 1) {
			pcbCtrl->wFlags |= CBF_INITIALIZED;
		}
		else {
			LPIDSTRLIST pItem = (LPIDSTRLIST)ComboBox_RemoveItem(hComboBox, 0);
			if(PtrIsValid(pItem)) {
				ComboBox_AddItems(hComboBox, pcbCtrl->pItems, pcbCtrl->nItems, pcbCtrl->pItems[0].nID, pItem->nID, (pcbCtrl->nType == DBVT_TCHAR) ? pItem->szText : NULL);
				FREE(pItem->szText);
				free(pItem);
				pcbCtrl->wFlags |= CBF_INITIALIZED;
				pcbCtrl->nTopItem = ComboBox_GetCurSel(hComboBox);
			}
		}
	}
}

static INT ComboBox_FindItemID(HWND hComboBox, INT nIndexStart, INT nID)
{
	LPIDSTRLIST pItem;

	while(PtrIsValid(pItem = (LPIDSTRLIST)ComboBox_GetItemData(hComboBox, nIndexStart))) {
		if(pItem->nID == nID)
			return nIndexStart;
		nIndexStart++;
	}
	return CB_ERR;
}

/**
 * name:	ComboBox_SelectItem
 * desc:	select one of the static items of a combobox by a setting of the database
 * param:	hDlg		- parent propertysheetpage dialogbox handle
 *			idCombo		- control id of the combobox whose item to select
 *			hContact	- contacht handle to read the settings of
 *			szModule	- default module to look in for the setting
 *			szBaseProto - basic protocol module of the contact to search the setting in, too
 *			szSetting	- the desired setting to read
 *			errorValue	- item to select if no setting was found anywhere
 *			isSigned	- whether the db value is signed or not
 *
 * return:	status of combobox, whether it is changed or not
 **/
BYTE ComboBox_SelectItem(
					HWND hDlg,
					INT idCombo,
					LPIDSTRLIST pItems,
					UINT nItems,
					HANDLE hContact,
					LPCSTR szModule,
					LPCSTR szBaseProto,
					LPCSTR szSetting,
					INT errorValue,
					BOOLEAN isSigned)
{
	HWND		hComboBox = GetDlgItem(hDlg, idCombo);
	LPCBCTRL	pcbCtrl;
	UINT		i;
	
	// allocate combobox's infostructure
	if(!PtrIsValid(pcbCtrl = (LPCBCTRL)GetUserData(hComboBox))) {
		if(!(pcbCtrl = (LPCBCTRL)malloc(sizeof(CBCTRL))))
			return CB_ERR;
		pcbCtrl->nType = CTRL_COMBOBOX_STAT;
		pcbCtrl->wFlags = 0;
		pcbCtrl->nTopItem = -1;
		pcbCtrl->pItems = pItems;
		pcbCtrl->nItems = nItems;

		SetUserData(hComboBox, pcbCtrl);
	}
	// if combobox has not changed yet, readout new settings from db
	if(!(pcbCtrl->wFlags & CTRLF_CHANGED)) {
		DBVARIANT dbv;
		LPIDSTRLIST pItem = NULL;

		LPCSTR szSubMod;
		if(!szModule) szSubMod = USERINFO;
		else szSubMod = szModule;

		// read setting from contact's basic protocol
		pcbCtrl->wFlags &= ~(CTRLF_HASCUSTOM|CTRLF_HASPROTO|CTRLF_HASMETA);
		pcbCtrl->wFlags |= DBGetSettingCtrlUnicode(hContact, szModule, szSubMod, szBaseProto, szSetting, &dbv);
		pcbCtrl->nType = dbv.type;
		// enable or disable combobox according to the usersettings
		EnableWindow(hComboBox, 
			!hContact ||
			(pcbCtrl->wFlags & CTRLF_HASCUSTOM) || 
			!(pcbCtrl->wFlags & (CTRLF_HASPROTO|CTRLF_HASMETA)) ||
			!DBGetByte(SET_PROPSHEET_PCBIREADONLY, 0));
		// combobox dropped down before
		if((pcbCtrl->wFlags & CBF_INITIALIZED)) {
			INT	iVal = CB_ERR;
			
			switch(dbv.type) {
				case DBVT_BYTE:
					iVal = ComboBox_FindItemID(hComboBox, 0, (INT) (isSigned ? dbv.cVal : dbv.bVal));
					break;
				case DBVT_WORD:
					iVal = ComboBox_FindItemID(hComboBox, 0, (INT)(isSigned ? dbv.sVal : dbv.wVal));
					break;
				case DBVT_TCHAR:
					iVal = SendMessage(hComboBox, CB_FINDSTRINGEXACT, 0, (LPARAM)TranslateTS(dbv.ptszVal));
					break;
			}
			pcbCtrl->nTopItem = iVal >= 0 ? iVal : errorValue;
		}
		// this is the first time, data is set for combobox
		else
		if(pcbCtrl->nTopItem == -1 && (pItem = (LPIDSTRLIST)malloc(sizeof(IDSTRLIST)))) {
			ZeroMemory(pItem, sizeof(IDSTRLIST));
			switch(dbv.type) {
				case DBVT_BYTE:
					pItem->nID = (INT)(isSigned ? dbv.cVal : dbv.bVal);
					for(i = 0; i < nItems; i++) {
						if(pItems[i].nID == pItem->nID) {
							pItem->szText = _tcsdup(pItems[i].szText);
							pItem->ptszTranslated = pItems[i].ptszTranslated ? pItems[i].ptszTranslated : TranslateTS(pItem->szText);
							break;
   						}
					}
					if(i == nItems)	{
						pItem->nID = pItems[0].nID;
						pItem->szText = _tcsdup(pItems[0].szText);
						pItem->ptszTranslated = pItems[0].ptszTranslated ? pItems[0].ptszTranslated : TranslateTS(pItem->szText);
					}						
					break;
				case DBVT_WORD:
					pItem->nID = (INT)(isSigned ? dbv.sVal : dbv.wVal);
					for(i = 0; i < nItems; i++)
						if(pItems[i].nID == pItem->nID) {
							pItem->szText = _tcsdup(pItems[i].szText);
							pItem->ptszTranslated = pItems[i].ptszTranslated ? pItems[i].ptszTranslated : TranslateTS(pItem->szText);
							break;
   						}
					if(i == nItems)	{
						pItem->nID = pItems[0].nID;
						pItem->szText = _tcsdup(pItems[0].szText);
						pItem->ptszTranslated = pItems[0].ptszTranslated ? pItems[0].ptszTranslated : TranslateTS(pItem->szText);
					}						
					break;
				case DBVT_TCHAR:
				{
					LPTSTR ptszVal = TranslateTS(dbv.ptszVal);

					for(i = 0; i < nItems; i++) {
						if(!_tcscmp(pItems[i].ptszTranslated, ptszVal)) {
							pItem->szText = _tcsdup(pItems[i].szText);
							pItem->ptszTranslated = TranslateTS(pItem->szText);
							break;
						}
					}
					if(i < nItems)
						break;
				}
				// first of each list is unspecified item
				default:
					pItem->nID = pItems->nID;
					pItem->szText = _tcsdup(pItems->szText);
					pItem->ptszTranslated = TranslateTS(pItem->szText);
					break;
			}

			pcbCtrl->nTopItem = ComboBox_AddItem(hComboBox, pItem->ptszTranslated, (LPARAM)pItem);
		}
		ComboBox_SetCurSel(hComboBox, pcbCtrl->nTopItem);
		pcbCtrl->wFlags &= ~CTRLF_CHANGED; 
		DBFreeVariant(&dbv);
	}
	return (pcbCtrl->wFlags & CTRLF_CHANGED);
}

/**
 * name:	ComboBox_WriteItem
 * desc:	writes the selected item to the database
 * param:	hDlg	  - owning dialogbox
 *			idCombo	  - control identifier for the combobox
 *			hContact  - handle to the contact the setting is to be written to
 *			szModule  - contacts module to write the setting in
 *			szSetting - setting to save
 *			bType	  - datatype the setting is to be written as
 *
 * return:	the datatype the setting was written as or DBVT_DELETED
 **/
BYTE ComboBox_WriteItem(HWND hDlg, INT idCombo, HANDLE hContact, LPCSTR szModule, LPCSTR szBaseProto, LPCSTR szSetting, BYTE bType, BOOLEAN isSigned)
{
	HWND hComboBox = GetDlgItem(hDlg, idCombo);
	LPCBCTRL pcbCtrl;
	INT nCurSel;
	LPIDSTRLIST pItem;

	if(hComboBox && PtrIsValid(pcbCtrl = (LPCBCTRL)GetUserData(hComboBox)) && (pcbCtrl->wFlags & CTRLF_CHANGED)) {
		// first item specifies undefined item, so delete setting
		if(0 < (nCurSel = ComboBox_GetCurSel(hComboBox)) && PtrIsValid(pItem = (LPIDSTRLIST)ComboBox_GetItemData(hComboBox, nCurSel))) {
			// save settings to base protocol if contact handle identifies owner!
			if(!hContact) szModule = szBaseProto;
			switch(bType) {
				case DBVT_BYTE:
					if(DBWriteContactSettingByte(hContact, szModule, szSetting, (BYTE)pItem->nID)) return DBVT_DELETED;
					if(hContact) pcbCtrl->wFlags |= CTRLF_HASCUSTOM;
					break;
				case DBVT_WORD:
					if(DBWriteContactSettingWord(hContact, szModule, szSetting, (WORD)pItem->nID)) return DBVT_DELETED;
					if(hContact) pcbCtrl->wFlags |= CTRLF_HASCUSTOM;
					break;
				case DBVT_ASCIIZ:
				{
#ifdef _UNICODE
					CHAR szText[MAXDATASIZE];
					BOOL bFail = FALSE;

					WideCharToMultiByte(myGlobals.CodePage, 0, pItem->szText, -1, szText, MAXDATASIZE, "?", &bFail);
					if(bFail && DBWriteContactSettingWString(hContact, szModule, szSetting, pItem->szText)) return DBVT_DELETED;
					else if(DBWriteContactSettingString(hContact, szModule, szSetting, szText)) return DBVT_DELETED;
#else
					if(DBWriteContactSettingString(hContact, szModule, szSetting, pItem->szText)) return DBVT_DELETED;
#endif
					if(hContact) pcbCtrl->wFlags |= CTRLF_HASCUSTOM;
					break;
				}
				case DBVT_WCHAR:
					if(DBWriteContactSettingTString(hContact, szModule, szSetting, pItem->szText)) return DBVT_DELETED;
					if(hContact) pcbCtrl->wFlags |= CTRLF_HASCUSTOM;
					break;
			}
		}
		else {
			DBDeleteContactSetting(hContact, szModule, szSetting);
			bType = DBVT_DELETED;
			pcbCtrl->wFlags &= ~CTRLF_HASCUSTOM;
			if(pcbCtrl->wFlags & CTRLF_HASPROTO) {
				pcbCtrl->wFlags &= ~CTRLF_CHANGED;
				ComboBox_SelectItem(hDlg, idCombo, NULL, NULL, hContact, NULL, szBaseProto, szSetting, 0, isSigned);
			}
		}
		pcbCtrl->nTopItem = ComboBox_GetCurSel(hComboBox);
		pcbCtrl->wFlags &= ~CTRLF_CHANGED;
		InvalidateRect(hComboBox, NULL, TRUE);
	}
	return bType;
}
